Skip to content

test: stabilize flaky calendar-1-single-booking Playwright test#467

Merged
turegjorup merged 1 commit into
release/3.0.0from
feature/playwright-calendar-stable-locator
May 27, 2026
Merged

test: stabilize flaky calendar-1-single-booking Playwright test#467
turegjorup merged 1 commit into
release/3.0.0from
feature/playwright-calendar-stable-locator

Conversation

@turegjorup

Copy link
Copy Markdown
Contributor

Why

calendar-1-single-booking: ui tests in assets/tests/template/template-calendar.spec.js is intermittently flaky — observed failing then passing on a Playwright rerun of the exact same commit, with no template/JSX changes between runs.

Root cause

The render that produces "Ledigt" / "Optaget" depends on currentTime, which the component initializes via useState(dayjs()) (line 70 of calendar-single-booking.jsx). A useEffect then calls intervalChecking() which resets currentTime to the (mocked) clock value and starts a 60s setInterval. Between mount and the first effect, the rendered .status text can briefly reflect non-mocked time. The instantBookingEnabled path also kicks off an async fetch to /action whose latency varies under CI load.

The previous assertion form amplified that race:

Old New
page.getByText("Ledigt") scans the whole DOM page.locator(".status") pins to one element
no stability anchor before the assertion preceded by await expect(page.locator(".room-info")).toBeVisible()
toHaveCount(1) + toBeVisible() fails fast if the text is briefly absent or duplicated toHaveText("Ledigt") keeps polling that one element until it settles

toHaveText against a specific locator is the auto-retrying pattern Playwright already documents for time-sensitive UI, and matches the passing calendar-0-single-booking sibling test (line 202).

What

 test("calendar-1-single-booking: ui tests", async ({ page }) => {
   await fixTime(page);
   await page.goto("/template/calendar-1-single-booking");
-  await expect(page.getByText("Ledigt")).toHaveCount(1);
-  await expect(page.getByText("Ledigt")).toBeVisible();
+  await expect(page.locator(".room-info")).toBeVisible();
+
+  const status = page.locator(".status");
+  await expect(status).toHaveText("Ledigt");

   await page.clock.runFor(61000);

-  await expect(page.getByText("Optaget")).toHaveCount(1);
-  await expect(page.getByText("Optaget")).toBeVisible();
+  await expect(status).toHaveText("Optaget");
 });

No production code touched.

Why not patch the component instead

Lazy-initializing currentTime from inside the effect (e.g. useState(null) then set on mount) would remove the brief "real time" render window, but risks the passing calendar-0-* sibling tests that already rely on the current behavior. Production change with a wider blast radius is the wrong tool for a test-side timing assumption — keep the fix where the problem is observed.

Test plan

  • CI Playwright green on this PR
  • No regression in the other calendar-* tests (they don't share the assertion form being changed)

🤖 Generated with Claude Code

Replace whole-DOM getByText("Ledigt") scan with a specific
.status locator and add a .room-info visibility anchor before
the assertion so Playwright doesn't poll an un-rendered tree.
Mirrors the pattern of the passing calendar-0-single-booking
sibling test (line 202).

The previous form was flaky: getByText with toHaveCount(1)
fails fast if .status briefly shows "Optaget" during the
mount-then-effect re-render window (currentTime initializes
from useState(dayjs()) before the page.clock.install propagates
into the React tree). toHaveText keeps polling the same element
until it settles.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@turegjorup
turegjorup merged commit f4fb41f into release/3.0.0 May 27, 2026
18 of 19 checks passed
@turegjorup
turegjorup deleted the feature/playwright-calendar-stable-locator branch May 27, 2026 11:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants